/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.commons.utils;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.zip.*;

/**
 * 数据的压缩/解压类
 */
public class ZipHelper {

    /**
     * 压缩方法
     *
     * @param input            待压缩内容
     * @param compressionLevel 压缩级别
     * @return 压缩后的byte数组
     */
    public static byte[] zipCompress(byte[] input, int compressionLevel) {
        if (input == null || input.length == 0) {
            return new byte[]{};
        }

        byte[] result;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, new Deflater(compressionLevel));
        try {
            deflaterOutputStream.write(input, 0, input.length);
            deflaterOutputStream.finish();
            result = byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException("数据流操作失败");
        } finally {
            if (deflaterOutputStream != null) {
                try {
                    deflaterOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            if (byteArrayOutputStream != null) {
                try {
                    byteArrayOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
        return result;
    }

    /**
     * 压缩方法
     *
     * @param srcStream        待压缩内容
     * @param zipStream        压缩后的流
     * @param compressionLevel 压缩级别
     * @param closeSrcStream   是否关闭srcStream
     */
    public static void zipCompress(InputStream srcStream, OutputStream zipStream, int compressionLevel, boolean closeSrcStream) {
        if (srcStream == null || zipStream == null) {
            return;
        }
        int bufferSize = 4096;  //缓冲区的尺寸，一般是2048的倍数
        byte[] buffer = new byte[bufferSize]; //创建缓冲数据
        DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(zipStream, new Deflater(compressionLevel));
        try {
            while ((bufferSize = srcStream.read(buffer)) > 0) {
                deflaterOutputStream.write(buffer, 0, bufferSize);
            }
        } catch (IOException e) {
            throw new RuntimeException("数据流操作失败");
        } finally {
            if (srcStream != null) {
                try {
                    if (closeSrcStream) {
                        srcStream.close();
                    }
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            if (deflaterOutputStream != null) {
                try {
                    deflaterOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
    }

    /**
     * 解压缩流
     *
     * @param zipStream    压缩后的流
     * @param outputStream 解压后的流
     * @param zipStream    压缩后是否关闭输入流
     */
    public static void zipDeCompress(InputStream zipStream, OutputStream outputStream, boolean closeZipStream) {
        if (zipStream == null || outputStream == null) {
            return;
        }
        int bufferSize = 4096;//缓冲区的尺寸，一般是2048的倍数
        InflaterInputStream inflaterInputStream = new InflaterInputStream(zipStream);
        int size = bufferSize;
        byte[] data = new byte[bufferSize];
        try {
            while (true) {
                size = inflaterInputStream.read(data, 0, size);
                if (size > 0) {
                    outputStream.write(data, 0, size);
                } else {
                    break;
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("数据流操作失败");
        } finally {
            if (inflaterInputStream != null) {
                try {
                    inflaterInputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            try {
                if (closeZipStream) {
                    zipStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("数据流关闭失败");
            }
        }
    }

    /**
     * 解压缩byte数组
     *
     * @param input 压缩的byte数组
     * @return 解压后的byte数组
     */
    public static byte[] zipDeCompress(byte[] input) {
        if (input == null || input.length == 0) {
            return new byte[]{};
        }
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(input);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] output = null;

        try {
            zipDeCompress(byteArrayInputStream, byteArrayOutputStream, true);
            output = byteArrayOutputStream.toByteArray();
        } finally {
            if (byteArrayInputStream != null) {
                try {
                    byteArrayInputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            if (byteArrayOutputStream != null) {
                try {
                    byteArrayOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
        return output;
    }

    /**
     * Gzip压缩方法
     *
     * @param srcStream      待压缩流
     * @param zipStream      压缩后的流
     * @param closeSrcStream 关闭源流
     */
    public static void gzipCompress(InputStream srcStream, OutputStream zipStream, boolean closeSrcStream) {
        if (srcStream == null || zipStream == null) {
            return;
        }
        int buffersize = 4096;//缓冲区的尺寸，一般是2048的倍数
        byte[] fileData = new byte[buffersize];//创建缓冲数据
        GZIPOutputStream gzipOutputStream = null;
        try {
            gzipOutputStream = new GZIPOutputStream(zipStream);
            while ((buffersize = srcStream.read(fileData, 0, buffersize)) > 0) {
                gzipOutputStream.write(fileData, 0, buffersize);
            }
        } catch (IOException e) {
            throw new RuntimeException("数据流操作失败");
        } finally {
            if (gzipOutputStream != null) {
                try {
                    gzipOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            try {
                if (closeSrcStream) {
                    srcStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("数据流关闭失败");
            }
        }
    }

    /**
     * gzip解压缩方法
     *
     * @param zipStream      需要解压缩的流
     * @param outputStream   解压后的流
     * @param closeZipStream 解压后是否关闭传入的压缩流
     */
    public static void gzipDeCompress(InputStream zipStream, OutputStream outputStream, boolean closeZipStream) {
        if (zipStream == null || outputStream == null) {
            return;
        }
        int buffersize = 4096;//缓冲区的尺寸，一般是2048的倍数
        byte[] fileData = new byte[buffersize];//创建缓冲数据
        GZIPInputStream gzipInputStream = null;
        try {
            gzipInputStream = new GZIPInputStream(zipStream);
            while ((buffersize = gzipInputStream.read(fileData, 0, buffersize)) > 0) {
                outputStream.write(fileData, 0, buffersize);
            }
        } catch (IOException e) {
            throw new RuntimeException("数据流操作失败");
        } finally {
            if (zipStream != null) {
                try {
                    if (closeZipStream) {
                        zipStream.close();
                    }
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            if (gzipInputStream != null) {
                try {
                    gzipInputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
    }


    /**
     * 压缩单个文件
     *
     * @param fileToZip        要压缩的文件
     * @param zipedFile        压缩后的文件
     * @param compressionLevel 压缩级别
     */
    public static void zipFile(String fileToZip, String zipedFile, int compressionLevel) {
        if (fileToZip == null || "".equals(fileToZip) || zipedFile == null || "".equals(zipedFile)) {
            return;
        }
        File inputFile = new File(fileToZip);
        if (!inputFile.exists()) {
            throw new RuntimeException(String.format("文件%s不存在", inputFile.getAbsoluteFile()));
        }
        File zipFile = new File(zipedFile);
        if (zipFile.exists()) {
            boolean result = zipFile.delete();
            if (!result) {
                throw new RuntimeException(String.format("文件%s删除失败", zipFile.getAbsoluteFile()));
            }
        }
        ZipOutputStream zipOutputStream = null;
        try {
            zipOutputStream = new ZipOutputStream(new FileOutputStream(zipedFile));
            writeZip(inputFile, "", zipOutputStream, compressionLevel);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } finally {
            if (zipOutputStream != null) {
                try {
                    zipOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
    }

    /**
     * 压缩多层目录
     *
     * @param sourceDirectoryName 待压缩的全路径名称
     * @param zipedFile           压缩后的文件，需要全路径
     * @param compressionLevel    压缩级别
     */
    public static void zipFileDirectory(String sourceDirectoryName, String zipedFile, int compressionLevel) {
        if (sourceDirectoryName == null || "".equals(sourceDirectoryName) || zipedFile == null || "".equals(zipedFile)) {
            return;
        }
        File directory = new File(sourceDirectoryName);
        if (!directory.exists() && !directory.isDirectory()) {
            throw new RuntimeException(String.format("文件%s不存在", directory.getAbsoluteFile()));
        }
        File zipFile = new File(zipedFile);
        if (zipFile.exists()) {
            throw new RuntimeException(String.format("文件%s已存在", zipFile.getAbsoluteFile()));
        }
        ZipOutputStream zipOutputStream = null;
        try {
            zipOutputStream = new ZipOutputStream(new FileOutputStream(zipedFile));
            writeZip(directory, "", zipOutputStream, compressionLevel);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } finally {
            if (zipOutputStream != null) {
                try {
                    zipOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
    }

    /**
     * 压缩
     *
     * @param file             待压缩文件
     * @param parentPath       父路径
     * @param zos              zip输出流
     * @param compressionLevel 压缩级别
     */
    private static void writeZip(File file, String parentPath, ZipOutputStream zos, int compressionLevel) {
//        ||"".equals(parentPath)
        if (file == null || parentPath == null || zos == null) {
            return;
        }
        zos.setLevel(compressionLevel);
        if (file.isDirectory()) {    //处理文件夹
            parentPath += file.getName() + File.separator;
            File[] files = file.listFiles();
            if (files.length != 0) {
                for (File f : files) {
                    writeZip(f, parentPath, zos, compressionLevel);
                }
            } else {    //空目录则创建当前目录
                try {
                    zos.putNextEntry(new ZipEntry(parentPath));
                } catch (IOException e) {
                    throw new RuntimeException("数据流操作失败");
                }
            }
        } else {
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(file);
                ZipEntry ze = new ZipEntry(parentPath + file.getName());//创建压缩文件
                zos.putNextEntry(ze);//添加压缩文件
                int buffersize = 4096;//缓冲区的尺寸，一般是2048的倍数
                byte[] fileData = new byte[buffersize];//创建缓冲数据

                while ((buffersize = fis.read(fileData, 0, buffersize)) > 0) {
                    zos.write(fileData, 0, buffersize);
                }
            } catch (IOException e) {
                throw new RuntimeException("数据流操作失败");
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
    }

    /**
     * 解压缩一个zip文件
     *
     * @param zipedFile          压缩文件，需要全路径
     * @param unZipDirectoryPath 解压文件所在路径，需要全路径
     */
    public static void unZip(String zipedFile, String unZipDirectoryPath) {
        if (zipedFile == null || "".equals(zipedFile)) {
            return;
        }
        File zip = new File(zipedFile);
        if (unZipDirectoryPath == null || "".equals(unZipDirectoryPath)) {
            unZipDirectoryPath = zip.getAbsolutePath().substring(0, zip.getAbsolutePath().lastIndexOf("."));
        }
        File outputDir = checkDirectoryExist(unZipDirectoryPath);

        FileOutputStream out = null;
        InputStream in = null;
        //读出文件数据
        ZipFile zipFileData = null;
        ZipFile zipFile = null;
        try {
            //指定编码
            zipFile = new ZipFile(zipedFile, Charset.forName("utf8"));
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            //处理创建文件夹
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String filePath = outputDir.getPath() + File.separator + entry.getName();
                File file = new File(filePath);
                File parentFile = file.getParentFile();
                if (!parentFile.exists()) {
                    boolean result = parentFile.mkdirs();
                    if (!result) {
                        throw new RuntimeException(String.format("文件夹%s创建失败", parentFile.getAbsoluteFile()));
                    }
                }
                if (parentFile.isDirectory()) {
                    continue;
                }
            }
            //解压缩
            zipFileData = new ZipFile(zip.getPath(), Charset.forName("utf8"));
            Enumeration<? extends ZipEntry> entriesData = zipFileData.entries();
            while (entriesData.hasMoreElements()) {
                ZipEntry entry = entriesData.nextElement();
                in = zipFile.getInputStream(entry);
                String filePath = outputDir.getPath() + File.separator + entry.getName();
                File file = new File(filePath);
                if (file.isDirectory()) {
                    continue;
                }
                out = new FileOutputStream(filePath);
                int buffersize = 4096;//缓冲区的尺寸，一般是2048的倍数
                byte[] fileData = new byte[buffersize];//创建缓冲数据
                while ((buffersize = in.read(fileData, 0, buffersize)) > 0) {
                    out.write(fileData, 0, buffersize);
                }
                out.flush();
                //文件夹解压时需要将所有文件流都关闭
                try {
                    if (out != null) {
                        out.close();
                    }

                    if (in != null) {
                        in.close();
                    }
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("数据流操作失败");
        } finally {
            //这里输入流输出流关闭可以去除，为确保完全关闭，暂时保留
            try {
                if (out != null) {
                    out.close();
                }

                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("数据流关闭失败");
            }
            try {
                if (zipFile != null) {
                    zipFile.close();
                }

                if (zipFileData != null) {
                    zipFileData.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("文件关闭失败");
            }

        }
    }

    /**
     * 判断文件目录是否存在，不存在则创建，若存在则抛异常
     *
     * @param dirPath 目录路径
     * @return 文件目录
     */
    private static File checkDirectoryExist(String dirPath) {
        File file = new File(dirPath);
        if (file.exists()) {
            throw new RuntimeException(String.format("文件%s已存在", file.getAbsoluteFile()));
        }
        if (!file.exists()) {
            boolean result = file.mkdirs();
            if (!result) {
                throw new RuntimeException(String.format("文件夹%s创建失败", file.getAbsoluteFile()));
            }
        }
        return file;
    }
}
